<?php

/**
 * 公共函数文件
 */

/**
 * 加载函数库
 *
 *     load_functions('tag', ...)
 *     load_functions(array('tag', ...))
 *
 * @param string|array $names
 */
function load_functions($names)
{
    static $cached = array('common');

    if (func_num_args() > 1) {
        $names = func_get_args();
    } elseif (! is_array($names)) {
        $names = array($names);
    }

    $names = array_map('strtolower', $names);

    foreach ($names as $name) {
        if (! isset($cached[$name])) {
            $file = APP_PATH . "/functions/{$name}.php";
            if (is_file($file)) {
                require_once $file;
            }
        }
    }
}

/**
 * 项目更新时间
 *
 * @return integer
 */
function app_update_time()
{
    static $time = null;

    if ($time === null) {
        if ($cache = cache_get('app_update_time')) return $cache;

        $time = time();
        cache_save('app_update_time', $time);
    }

    return $time;
}

/**
 * 加载配置文件数据
 *
 *     config('database')
 *     config('database.default.adapter')
 *
 * @param  string $name
 * @return mixed
 */
function config($name, $default = null)
{
    static $cached = array();

    // 移除多余的分隔符
    $name = trim($name, '.');

    if (! isset($cached[$name])) {
        // 获取配置名及路径
        if (strpos($name, '.') === false) {
            $paths = array();
            $filename = $name;
        } else {
            $paths = explode('.', $name);
            $filename = array_shift($paths);
        }

        if (! isset($cached[$filename])) {
            // 查找配置文件
            $file = APP_PATH . '/config/' . $filename . '.php';
            if (! is_file($file)) {
                return $default;
            }

            // 从文件中加载配置数据
            $data = include $file;
            if (is_array($data)) {
                $data = new \Phalcon\Config($data);
            }

            // 缓存文件数据
            $cached[$filename] = $data;
        } else {
            $data = $cached[$filename];
        }

        // 支持路径方式获取配置，例如：config('file.key.subkey')
        foreach ($paths as $key) {
            if (is_array($data) && isset($data[$key])) {
                $data = $data[$key];
            } elseif (is_object($data) && isset($data->{$key})) {
                $data = $data->{$key};
            } else {
                $data = null;
            }
        }

        // 缓存数据
        $cached[$name] = $data;
    }

    return $cached[$name] === null ? $default : $cached[$name];
}

/**
 * 简化 \Phalcon\Di::getDefault()->getShared($service)
 *
 *     service('url')
 *     service('db')
 *     ...
 *
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_DI.html
 * @param  string $service
 * @return mixed
 */
function service($service)
{
    return \Phalcon\DI::getDefault()->getShared($service);
}

/**
 * 获取完整的 url 地址
 *
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_Url.html
 * @param  string $uri
 * @return string
 */
function url($uri = null)
{
    // 网址链接及非正常的 url，纯锚点 (#...) 和 (javascript:)
    if (preg_match('~^(#|javascript:|https?://|telnet://|ftp://|tencent://)~', $uri)) {
        return $uri;
    }

    return service('url')->get(ltrim($uri, '/'));
}

/**
 * 获取静态资源地址
 *
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_Url.html
 * @param  string $uri
 * @param  string $time
 * @return string
 */
function static_url($uri = null, $time = true)
{
    if (! preg_match('~t=\d+$~i', $uri) && $time) {
        $params = array('t' => PRODUCTION ? app_update_time() : time());
    } else {
        $params = null;
    }

    if (! preg_match('~^https?://~i', $uri)) {
        $uri = service('url')->getStatic(ltrim($uri, '/'));
    }

    return url_param($uri, $params);
}

/**
 * 获取包含域名在内的 url
 *
 * @param  string $uri
 * @param  string $base
 * @return string
 */
function baseurl($uri = null, $base = HTTP_BASE)
{
    return HTTP_BASE . ltrim($uri, '/');
}

/**
 * 根据 query string 参数生成 url
 *
 *     url_param('item/list', array('page' => 1)) // item/list?page=1
 *     url_param('item/list?page=1', array('limit' => 10)) // item/list?page=1&limit=10
 *
 * @param  string $uri
 * @param  array  $params
 * @return string
 */
function url_param($uri = null, array $params = null, $decode = false)
{
    if ($uri === null) {
        $uri = HTTP_URL;
    }

    if (empty($params)) {
        return $uri;
    }

    $parts = parse_url($uri);
    $queries = array();
    if (isset($parts['query']) && $parts['query']) {
        parse_str($parts['query'], $queries);
    }

    $params = array_filter(
        array_merge($queries, $params),
        function ($item) {
            return $item !== '' && $item !== null;
        }
    );

    // 重置 query 组件
    $parts['query'] = rawurldecode(http_build_query($params));

    return http_build_url($uri, $parts);
}

/**
 * 使用 sprintf 对 url 进行格式化
 *
 *      furl('item/%s-%d.html', 'books', 1) // /item/books-1.html
 *
 * @return string
 */
function furl()
{
    return url(call_user_func_array('sprintf', func_get_args()));
}

/**
 * 获取视图内容
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/views.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_View.html
 * @return string
 */
function get_content()
{
    return service('view')->getContent();
}

/**
 * 判断视图是否存在
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/views.html
 * @param  string       $viewFile
 * @param  string|array $suffixes
 * @return boolean
 */
function has_view($viewFile, $suffixes = null)
{
    $file = service('view')->getViewsDir() . $viewFile;

    if ($suffixes === null) {
        $suffixes = array('phtml', 'volt');
    } elseif (! is_array($suffixes)) {
        $suffixes = array($suffixes);
    }

    foreach ($suffixes as $suffix) {
        if (is_file($file . '.' . $suffix)) {
            return true;
        }
    }

    return false;
}

/**
 * 加载局部视图
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/views.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_View.html
 * @param  string $partialPath
 * @param  array  $params
 * @return string
 */
function partial_view($partialPath, array $params = null)
{
    return service('view')->partial($partialPath, $params);
}

/**
 * 选择不同的视图来渲染，并做为最后的 controller/action 输出
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/views.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_View.html
 * @param  string $renderView
 * @return string
 */
function pick_view($renderView)
{
    return service('view')->pick($renderView);
}

/**
 * 为视图设定值
 *
 * @see   http://docs.phalconphp.com/zh/latest/reference/views.html
 * @see   http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_View.html
 * @param string|array $key
 * @param mixed        $value
 */
function set_var($key, $value = null)
{
    if (is_array($key)) {
        service('view')->setVars($key, true);
    } else {
        service('view')->setVar($key, $value);
    }
}

/**
 * 获取视图设定的值
 *
 * @see   http://docs.phalconphp.com/zh/latest/reference/views.html
 * @see   http://docs.phalconphp.com/zh/latest/api/Phalcon_Mvc_View.html
 * @param string $key
 * @param mixed  $default
 */
function get_var($key, $default = null)
{
    return isset(service('view')->$key) ? service('view')->$key : $default;
}

/**
 * @see    http://docs.phalconphp.com/zh/latest/reference/request.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Http_Request.html
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function get_query($name = null, $default = null)
{
    return service('request')->getQuery($name, null, $default);
}

/**
 * @see    http://docs.phalconphp.com/zh/latest/reference/request.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Http_Request.html
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function get_post($name = null, $default = null)
{
    return service('request')->getPost($name, null, $default);
}

/**
 * @see    http://docs.phalconphp.com/zh/latest/reference/request.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Http_Request.html
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function get_put($name = null, $default = null)
{
    return service('request')->getPut($name, null, $default);
}

/**
 * @see    http://docs.phalconphp.com/zh/latest/reference/request.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Http_Request.html
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function get_server($name = null, $default = null)
{
    return service('request')->hasServer($name) ? service('request')->getServer($name) : $default;
}

/**
 * 获取重定向 url
 *
 * @param  string $name
 * @param  string $default
 * @return string
 */
function get_redirect($name, $default = null)
{
    if (! ($url = get_query($name)) || preg_match('~^' . preg_quote(HTTP_BASE) . '~i', $url)) {
        $url = url($default);
    }

    return preg_replace('~^' . rtrim(preg_quote(HTTP_BASE), '/') . '~i', '', $url);
}

/**
 * 语言转换
 *
 *     // file: ~/app/i18n/{$lang}/category.item.php
 *     __('category.item.list')
 *
 *     // data: array('welcome' => 'Hello, :name')
 *     __('welcome', array(':name' => 'zhouyl')) // Hello, zhouyl
 *
 * @param  string $string 要转换的字符串，默认传入中文
 * @param  array  $values 需要替换的参数
 * @param  string $lang   指定的语言类型
 * @return string
 */
function __($string, array $values = null, $lang = null)
{
    return service('i18n')->translate($string, $values, $lang);
}

/**
 * 调用 OaTranslates::read 读取翻译结果
 *
 * @param  string  $default
 * @param  string  $keyword
 * @param  integer $foreign_id
 * @param  string  $lang
 * @return string
 */
function db_translate($default, $keyword, $foreign_id, $lang = null)
{
    if ($lang === null) {
        $lang = service('i18n')->getDefault();
    }

    if ($lang !== 'en-us' && $string = OaTranslates::read($keyword, $foreign_id, $lang)) {
        return $string;
    }

    return $default;
}

/**
 * 简化三元表达式
 *
 * @param  $boolean $boolValue
 * @param  mixed    $trueValue
 * @param  mixed    $falseValue
 * @return mixed
 */
function on($boolValue, $trueValue, $falseValue = null)
{
    return $boolValue ? $trueValue : $falseValue;
}

/**
 * 返回格式化的 json 数据
 *
 * @param  array   $array
 * @param  boolean $pretty    美化 json 数据
 * @param  boolean $unescaped 关闭 Unicode 编码
 * @return string
 */
function json_it(array $array, $pretty = true, $unescaped = true)
{
    // php 5.4+
    if (defined('JSON_PRETTY_PRINT') && defined('JSON_UNESCAPED_UNICODE')) {
        if ($pretty && $unescaped)
            $options = JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE;
        elseif ($pretty)
            $options = JSON_PRETTY_PRINT;
        elseif ($unescaped)
            $options = JSON_UNESCAPED_UNICODE;
        else
            $options = null;

        return json_encode($array, $options);
    }

    if ($unescaped) {
        // convmap since 0x80 char codes so it takes all multibyte codes (above ASCII 127).
        // So such characters are being "hidden" from normal json_encoding
        $tmp = array();
        array_walk_recursive($array, function (&$item, $key) {
            if (is_string($item)) {
                $item = mb_encode_numericentity($item, array(0x80, 0xffff, 0, 0xffff), 'UTF-8');
            }
        });
        $json = mb_decode_numericentity(json_encode($array), array(0x80, 0xffff, 0, 0xffff), 'UTF-8');
    } else {
        $json = json_encode($array);
    }

    if ($pretty) {
        $result      = '';
        $pos         = 0;
        $strLen      = strlen($json);
        $indentStr   = "\t";
        $newLine     = "\n";
        $prevChar    = '';
        $outOfQuotes = true;

        for ($i = 0; $i <= $strLen; $i++) {

            // Grab the next character in the string.
            $char = substr($json, $i, 1);

            // Are we inside a quoted string
            if ($char == '"' AND $prevChar != '\\') {
                $outOfQuotes = ! $outOfQuotes;

            // If this character is the end of an element,
            // output a new line and indent the next line.
            } elseif (($char == '}' OR $char == ']') AND $outOfQuotes) {
                $result .= $newLine;
                $pos--;
                for ($j = 0; $j < $pos; $j++) {
                    $result .= $indentStr;
                }
            }

            // Add the character to the result string.
            $result .= $char;

            // If the last character was the beginning of an element,
            // output a new line and indent the next line.
            if (($char == ',' OR $char == '{' OR $char == '[') AND $outOfQuotes) {
                $result .= $newLine;
                if ($char == '{' OR $char == '[') {
                    $pos++;
                }

                for ($j = 0; $j < $pos; $j++) {
                    $result .= $indentStr;
                }
            }

            $prevChar = $char;
        }

        $json = $result;
    }

    return $json;
}

/**
 * 简化日志写入方法
 *
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Logger.html
 * @see    http://docs.phalconphp.com/zh/latest/api/Phalcon_Logger_Adapter_File.html
 * @param  string $name    日志名称
 * @param  string $message 日志内容
 * @param  string $type    日志类型
 * @return \Phalcon\Logger\Adapter\File
 */
function write_log($name, $message, $type = null)
{
    static $logger, $formatter;

    if (! isset($logger[$name])) {
        $logfile = DATA_PATH . '/logs/' . date('/Ym/') . $name . '_' . date('Ymd') . '.log';
        if (! is_dir(dirname($logfile))) {
            mkdir(dirname($logfile), 0755, true);
        }

        $logger[$name] = new \Phalcon\Logger\Adapter\File($logfile);

        // Set the logger format
        if ($formatter === null) {
            $formatter = new \Phalcon\Logger\Formatter\Line();
            $formatter->setDateFormat('Y-m-d H:i:s O');
        }

        $logger[$name]->setFormatter($formatter);
    }

    if ($type === null) {
        $type = \Phalcon\Logger::INFO;
    }

    if ($message) {
        $logger[$name]->log(trim($message), $type);
    }

    return $logger[$name];
}

/**
 * 隐藏当前系统路径
 *
 *     strip_path('/web/myapp/app/config/db.php') // ~/app/config/db.php
 *
 * @param  string $path
 * @return string
 */
function strip_path($path)
{
    return str_replace(ROOT_PATH, '~', $path);
}

/**
 * Email格式检查 (支持验证host有效性)
 *
 * @param  string  $email
 * @param  boolean $testMX
 * @return boolean
 */
function is_email($email, $testMX = false)
{
    if (preg_match('/^([_a-z0-9+-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/i', $email)) {
        if ($testMX) {
            list( , $domain) = explode("@", $email);

            return getmxrr($domain, $mxrecords);
        }

        return true;
    }

    return false;
}

/**
 * 检查是否效的 url
 *
 * @param  string  $url
 * @return boolean
 */
function is_url($url)
{
    return (boolean) preg_match('/^https?:\/\/([a-z0-9\-]+\.)+[a-z]{2,3}([a-z0-9_~#%&\/\'\+\=\:\?\.\-])*$/i', $url);
}

/**
 * 判断一个 yyyymmdd 格式的字符串是否为一个日期
 *
 * @param  string $ymd
 * @return boolean
 */
function is_date($ymd)
{
    $ymd = str_replace(array('/', '-'), '', $ymd);

    return checkdate(substr($ymd, 4, 2), substr($ymd, 6, 2), substr($ymd, 0, 4));
}

/**
 * 是否中文字符 (包括全角字符)
 *
 * @param  string  $str
 * @return boolean
 */
function is_chinese($str)
{
    return (boolean) preg_match('/[\x{4E00}-\x{9FA5}\x{FE30}-\x{FFA0}\x{3000}-\x{3039}]/u', $str);
}

/**
 * CURL POST 请求
 *
 * @param  string $url
 * @param  array  $postdata
 * @param  array  $curl_opts
 * @return string
 */
function curl_post($url, array $postdata = null, array $curl_opts = null)
{
    $ch = curl_init();

    if ($postdata !== null) {
        $postdata = http_build_query($postdata);
    }

    curl_setopt_array($ch, array(
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_URL            => $url,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_POST           => 1,
        CURLOPT_POSTFIELDS     => $postdata,
        CURLOPT_RETURNTRANSFER => 1,
    ));

    if ($curl_opts !== null) {
        curl_setopt_array($ch, $curl_opts);
    }

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}

/**
 * CURL GET 请求
 *
 * @param  string $url
 * @param  array  $curl_opts
 * @return string
 */
function curl_get($url, array $curl_opts = null)
{
    $ch = curl_init();

    curl_setopt_array($ch, array(
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_URL            => $url,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_RETURNTRANSFER => 1,
    ));

    if ($curl_opts !== null) {
        curl_setopt_array($ch, $curl_opts);
    }

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}

/**
 * 写入缓存
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/cache.html
 * @param  string  $key
 * @param  mixed   $data
 * @param  integer $lifetime
 * @param  boolean $stopBuffer
 */
function cache_save($key, $data, $lifetime = 86400, $stopBuffer = false)
{
    return service('cache')->save($key . '.cache', $data, $lifetime, $stopBuffer);
}

/**
 * 获取缓存
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/cache.html
 * @param  string $key
 * @param  mixed  $default
 * @return mixed
 */
function cache_get($key, $default = null)
{
    $cache = service('cache')->get($key . '.cache');

    return $cache === null ? $default : $cache;
}

/**
 * 删除缓存
 *
 * @see    http://docs.phalconphp.com/zh/latest/reference/cache.html
 * @param  string $key
 * @return boolean
 */
function cache_del($key)
{
    return service('cache')->delete($key . '.cache');
}

/**
 * 设置 cookie 值
 *
 * @param string  $name
 * @param mixed   $value
 * @param integer $expire
 */
function cookie_set($name, $value, $expire = null)
{
    return service('cookies')->set($name, $value, $expire)->send();
}

/**
 * 获取 cookie 值
 *
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function cookie_get($name, $default = null)
{
    $value = service('cookies')->get($name)->getValue();

    return $value === null ? $default : $value;
}

/**
 * 删除 cookie
 *
 * @param  string $name
 * @return boolean
 */
function cookie_del($name)
{
    return service('cookies')->delete($name);
}

/**
 * 设置 session 值
 *
 * @see   http://docs.phalconphp.com/zh/latest/reference/session.html
 * @see   http://docs.phalconphp.com/zh/latest/api/Phalcon_Session_AdapterInterface.html
 * @param string $name
 * @param mixed  $value
 */
function session_set($name, $value = null)
{
    if (is_array($name)) {
        foreach ($name as $k => $v) {
            service('session')->set($k, $v);
        }
    } else {
        service('session')->set($name, $value);
    }

    return service('session');
}

/**
 * 获取 session 值
 *
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function session_get($name, $default = null)
{
    return service('session')->get($name, $default);
}

/**
 * 删除 session
 *
 * @param  string $name
 * @return boolean
 */
function session_del($name)
{
    return service('session')->remove($name);
}

/**
 * 获取某个浮点数的 digit，比如 12.39719 返回 5
 *
 * @param  float  $number
 * @return int
 */
function get_digits($number)
{
    return strlen(preg_replace('/\d+\./', '', $number));
}

/**
 * 因 number_format 默认参数带来的千分位是逗号的hack
 * 功能和 number_format一致，只是设定了固定的第3，4个参数
 *
 * @param  mixed   $number
 * @param  integer $decimals
 * @return mixed
 */
function num_format($number, $decimals = 0)
{
    return number_format($number , $decimals, '.', '');
}

/**
 * 按指定的长度切割字符串
 *
 * @param  string  $string 需要切割的字符串
 * @param  integer $length 长度
 * @param  string  $suffix 切割后补充的字符串
 * @return string
 */
function str_break($string, $length, $suffix = '')
{
    if (strlen($string) <= $length + strlen($suffix)) {
        return $string;
    }

    $n = $tn = $noc = 0;
    while ($n < strlen($string)) {
        $t = ord($string[$n]);
        if ($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {
            $tn = 1; $n++; $noc++;
        } elseif (194 <= $t && $t <= 223) {
            $tn = 2; $n += 2; $noc += 2;
        } elseif (224 <= $t && $t < 239) {
            $tn = 3; $n += 3; $noc += 2;
        } elseif (240 <= $t && $t <= 247) {
            $tn = 4; $n += 4; $noc += 2;
        } elseif (248 <= $t && $t <= 251) {
            $tn = 5; $n += 5; $noc += 2;
        } elseif ($t == 252 || $t == 253) {
            $tn = 6; $n += 6; $noc += 2;
        } else {
            $n++;
        }
        if ($noc >= $length) {
            break;
        }
    }
    $noc > $length && $n -= $tn;
    $strcut = substr($string, 0, $n);
    if (strlen($strcut) < strlen($string)) {
        $strcut .= $suffix;
    }

    return $strcut;
}

/**
 * 字符串高亮
 *
 * @param  string  $string  需要的高亮的字符串
 * @param  mixed   $keyword 关键字，可以是一个数组
 * @return string
 */
function highlight_keyword($string, $keyword)
{
    $string = (string) $string;

    if ($string && $keyword) {
        if (! is_array($keyword)) {
            $keyword = array($keyword);
        }

        $pattern = array();
        foreach ($keyword as $word) {
            if (! empty($word)) {
                $pattern[] = '(' . str_replace('/', '\/',  preg_quote($word)) . ')';
            }
        }

        if (! empty($pattern)) {
            $string = preg_replace(
                '/(' . implode('|', $pattern) . ')/is',
                '<span style="background:#FF0;color:#E00;">\\1</span>',
                $string
            );
        }
    }

    return $string;
}

/**
 * 将 HTML 转换为文本
 *
 * @param  string $html
 * @return string
 */
function html2txt($html)
{
    $html = trim($html);
    if (empty($html))
        return $html;
    $search = array("'<script[^>]*?>.*?</script>'si",
        "'<style[^>]*?>.*?</style>'si",
        "'<[\/\!]*?[^<>]*?>'si",
        "'([\r\n])[\s]+'",
        "'&(quot|#34);'i",
        "'&(amp|#38);'i",
        "'&(lt|#60);'i",
        "'&(gt|#62);'i",
        "'&(nbsp|#160)[;]*'i",
        "'&(iexcl|#161);'i",
        "'&(cent|#162);'i",
        "'&(pound|#163);'i",
        "'&(copy|#169);'i",
        "'&#(\d+);'e"
    );
    $replace = array("", "", "", "\\1", "\"", "&", "<", ">", " ",
                     chr(161), chr(162), chr(163), chr(169), "chr(\\1)");

    return preg_replace($search, $replace, $html);
}

/**
 * 从数组中获取值，如果未设定时，返回默认值
 *
 * @param  array  $array
 * @param  string $name
 * @param  mixed  $default
 * @return mixed
 */
function array_get($array, $name, $default = null)
{
    return isset($array[$name]) ? $array[$name] : $default;
}

/**
 * 递归地合并一个或多个数组(不同于 array_merge_recursive)
 *
 * @return array
 */
function array_merge_deep()
{
    $a = func_get_args();
    for ($i = 1; $i < count($a); $i++) {
        foreach ($a[$i] as $k => $v) {
            if (isset($a[0][$k])) {
                if (is_array($v)) {
                    if (is_array($a[0][$k])) {
                        $a[0][$k] = array_merge_deep($a[0][$k], $v);
                    } else {
                        $v[] = $a[0][$k];
                        $a[0][$k] = $v;
                    }
                } else {
                    $a[0][$k] = is_array($a[0][$k]) ? array_merge($a[0][$k], array($v)) : $v;
                }
            } else {
                $a[0][$k] = $v;
            }
        }
    }

    return $a[0];
}

/**
 * 移除数组中的 null 值
 *
 * @param  array $array
 * @return array
 */
function array_remove_null(array $array)
{
    return array_filter($array, function ($val) {
        return ! is_null($val);
    });
}

/**
 * 用回调函数，根据数组键&值，过滤数组中的单元
 *
 * @param  array  $array
 * @param  mixed  $callback
 * @return array
 */
function array_filter_full(array $array, $callback)
{
    if (! is_callable($callback)) {
        trigger_error(__FUNCTION__.'() expects parameter 2 to be a valid callback', E_USER_ERROR);
    }

    return $array = array_filter($array, function ($val) use (& $array, $callback) {
        $key = key($array);
        next($array);

        return (bool) $callback($key, $val);
    });
}

/**
 * 将数据转换为字符，并在同一行输出
 *
 * @param  array  $array
 * @param  string $separator
 * @return string
 */
function array_join_inline(array $array, $separator = ', ')
{
    $tmp = array();
    foreach ($array as $key => $val) {
        $tmp[] = "$key: " . (is_array($val) ? array_join_line($val, $separator) : $val);
    }

    return implode($separator, $tmp);
}

/**
 * 使用一个二维数组中的某一个key作键值，返回一个新的数组
 *
 *     $a = array(
 *         'a' => array('id' => 1, 'name' => 'x'),
 *         'b' => array('id' => 2, 'name' => 'y'),
 *         'c' => array('id' => 3, 'name' => 'z'),
 *     );
 *     array_using_key($a, 'name') // array('x' => ..., 'y' => ..., ...)
 *
 * @param  array  $array
 * @param  string key
 * @return array
 */
function array_using_key($array, $key)
{
    $result = array();

    foreach ($array as $r) {
        if (isset($r[$key])) {
            $result[$r[$key]] = $r;
        }
    }

    return $result;
}

/**
 * 用回调函数，根据数组键值，过滤数组中的单元
 *
 * @param  array    $array
 * @param  callable $callback
 * @return array
 */
function array_filter_by_key(array & $array, $callback)
{
    if (! is_callable($callback)) {
        trigger_error(__FUNCTION__ . '() expects parameter 2 to be a valid callback', E_USER_ERROR);
    }

    return $array = array_filter($array, function ($val) use (& $array, $callback) {
        $key = key($array);
        next($array);

        return (bool) $callback($key);
    });
}

/**
 * 从一个二维数组中选取指定字段并返回
 *
 *     $a = array(
 *         'a' => array('id' => 1, 'name' => 'x', 'value' => 'a'),
 *         'b' => array('id' => 2, 'name' => 'y', 'value' => 'b'),
 *         'c' => array('id' => 3, 'name' => 'z', 'value' => 'c'),
 *     );
 *
 *     array_pick($a, 'name') // array('a' => 'x', 'b' => 'y', ...)
 *     array_pick($a, array('id', 'value')) // array('a' => array('id' => 1, 'value' => 'a'), ...)
 *
 * @param  array        $array 要选取的数组
 * @param  array|string $field 要选取的字段
 * @return array
 */
function array_pick($array, $field)
{
    return array_map(function ($item) use ($field) {
        if (is_array($field)) {
            $result = array();
            foreach ($field as $f) {
                $result[$f] = array_get($item, $f);
            }

            return $result;
        } else {
            return array_get($item, $field);
        }
    }, (array) $array);
}

if (! function_exists('lcwords')) {
    /**
     * Lowercase the first character of each word in a string
     *
     * @param  string $string
     * @return string
     */
    function lcwords($string)
    {
        $tokens = explode(' ', $string);
        if (! is_array($tokens) || count($tokens) <= 1) {
            return lcfirst($string);
        }

        $result = array();
        foreach ($tokens as $token) {
            $result[] = lcfirst($token);
        }

        return implode(' ', $result);
    }
}

if (! function_exists('http_build_url')) {
    define('HTTP_URL_REPLACE', 1);          // Replace every part of the first URL when there's one of the second URL
    define('HTTP_URL_JOIN_PATH', 2);        // Join relative paths
    define('HTTP_URL_JOIN_QUERY', 4);       // Join query strings
    define('HTTP_URL_STRIP_USER', 8);       // Strip any user authentication information
    define('HTTP_URL_STRIP_PASS', 16);      // Strip any password authentication information
    define('HTTP_URL_STRIP_AUTH', 32);      // Strip any authentication information
    define('HTTP_URL_STRIP_PORT', 64);      // Strip explicit port numbers
    define('HTTP_URL_STRIP_PATH', 128);     // Strip complete path
    define('HTTP_URL_STRIP_QUERY', 256);    // Strip query string
    define('HTTP_URL_STRIP_FRAGMENT', 512); // Strip any fragments (#identifier)
    define('HTTP_URL_STRIP_ALL', 1024);     // Strip anything but scheme and host

    /**
     * Build an URL
     * The parts of the second URL will be merged into the first according to the flags argument.
     *
     * @param mixed   (Part(s) of) an URL in form of a string or associative array like parse_url() returns
     * @param mixed   Same as the first argument
     * @param integer A bitmask of binary or'ed HTTP_URL constants (Optional)HTTP_URL_REPLACE is the default
     * @param array   If set, it will be filled with the parts of the composed url like parse_url() would return
     */
    function http_build_url($url, $parts = array(), $flags = HTTP_URL_REPLACE, & $new_url = false)
    {
        $keys = array('user', 'pass', 'port', 'path', 'query', 'fragment');

        // HTTP_URL_STRIP_ALL becomes all the HTTP_URL_STRIP_Xs
        if ($flags & HTTP_URL_STRIP_ALL) {
            $flags |= HTTP_URL_STRIP_USER;
            $flags |= HTTP_URL_STRIP_PASS;
            $flags |= HTTP_URL_STRIP_PORT;
            $flags |= HTTP_URL_STRIP_PATH;
            $flags |= HTTP_URL_STRIP_QUERY;
            $flags |= HTTP_URL_STRIP_FRAGMENT;
        }
        // HTTP_URL_STRIP_AUTH becomes HTTP_URL_STRIP_USER and HTTP_URL_STRIP_PASS
        elseif ($flags & HTTP_URL_STRIP_AUTH) {
            $flags |= HTTP_URL_STRIP_USER;
            $flags |= HTTP_URL_STRIP_PASS;
        }

        // Parse the original URL
        $parse_url = parse_url($url);

        // Scheme and Host are always replaced
        if (isset($parts['scheme']))
            $parse_url['scheme'] = $parts['scheme'];
        if (isset($parts['host']))
            $parse_url['host'] = $parts['host'];

        // (If applicable) Replace the original URL with it's new parts
        if ($flags & HTTP_URL_REPLACE) {
            foreach ($keys as $key) {
                if (isset($parts[$key]))
                    $parse_url[$key] = $parts[$key];
            }
        } else {
            // Join the original URL path with the new path
            if (isset($parts['path']) && ($flags & HTTP_URL_JOIN_PATH)) {
                if (isset($parse_url['path']))
                    $parse_url['path'] = rtrim(str_replace(basename($parse_url['path']), '', $parse_url['path']), '/') . '/' . ltrim($parts['path'], '/');
                else
                    $parse_url['path'] = $parts['path'];
            }

            // Join the original query string with the new query string
            if (isset($parts['query']) && ($flags & HTTP_URL_JOIN_QUERY)) {
                if (isset($parse_url['query']))
                    $parse_url['query'] .= '&' . $parts['query'];
                else
                    $parse_url['query'] = $parts['query'];
            }
        }

        // Strips all the applicable sections of the URL
        // Note: Scheme and Host are never stripped
        foreach ($keys as $key) {
            if ($flags & (integer) constant('HTTP_URL_STRIP_' . strtoupper($key)))
                unset($parse_url[$key]);
        }

        $new_url = $parse_url;

        return
            ((isset($parse_url['scheme'])) ? $parse_url['scheme'] . '://' : '') .
            ((isset($parse_url['user'])) ? $parse_url['user'] . ((isset($parse_url['pass'])) ? ':' . $parse_url['pass'] : '') .'@' : '') .
            ((isset($parse_url['host'])) ? $parse_url['host'] : '') .
            ((isset($parse_url['port'])) ? ':' . $parse_url['port'] : '') .
            ((isset($parse_url['path'])) ? $parse_url['path'] : '') .
            ((isset($parse_url['query'])) ? '?' . $parse_url['query'] : '') .
            ((isset($parse_url['fragment'])) ? '#' . $parse_url['fragment'] : '')
        ;
    }
}

/**
 * 转换驼峰式字符串为下划线风格
 *
 *     uncamel('lowerCamelCase') === 'lower_camel_case'
 *     uncamel('UpperCamelCase') === 'upper_camel_case'
 *     uncamel('ThisIsAString') === 'this_is_a_string'
 *     uncamel('notcamelcase') === 'notcamelcase'
 *     uncamel('lowerCamelCase', ' | ') === 'lower | camel | case'
 *
 * @param  string $string
 * @param  string $separator
 * @return string
 */
function uncamel($string, $separator = '_')
{
    $string = preg_replace('#(?<=[a-zA-Z])([A-Z])(?=[a-zA-Z])#e', "'$separator' . strtolower('$1')", $string);

    return lcfirst($string);
}

/**
 * 转换下划线字符串为驼峰式风格
 *
 *     camel('lower_camel_case') === 'lowerCamelCase'
 *     camel('upper_camel_case', true) === 'UpperCamelCase'
 *
 * @param  string $string
 * @param  string $lower
 * @return string
 */
function camel($string, $lower = false, $separator = '_')
{
    $result    = '';
    $words     = explode($separator, $string);
    $wordCount = count($words);
    for ($i = 0; $i < $wordCount; $i++) {
        $word = $words[$i];
        if (! ($i === 0 && $lower === false)) {
            $word = ucfirst($word);
        } else {
            $word = strtolower($word);
        }
        $result .= $word;
    }

    return $result;
}

/**
 * 获取文件上传器
 *
 * @param  array|string $keys
 * @param  array        $config
 * @return \Formax\Uploader
 */
function get_uploader($keys, array $config = null)
{
    is_array($keys) || $keys = array($keys);
    $uploader = \Formax\Uploader::factory($config);
    foreach (service('request')->getUploadedFiles() as $file) {
        if (in_array($file->getKey(), $keys)) {
            $uploader->save($file);
        }
    }

    return $uploader;
}

/**
 * 将 PDO 查询转换为 sql 语句
 *
 * @param  string $statement
 * @param  array  $params
 * @return string
 */
function pdo_statement_to_sql($statement, array $params = null)
{
    if (empty($params)) return $statement;

    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));

    return preg_replace($keys, $values, $statement, 1, $count);

}
